package com.dlc.shop.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.dlc.shop.bean.dto.FlowAnalysisDto;
import com.dlc.shop.bean.dto.FlowUserAnalysisDto;
import com.dlc.shop.bean.dto.SystemDto;
import com.dlc.shop.bean.enums.FlowLogPageEnum;
import com.dlc.shop.bean.enums.FlowTimeTypeEnum;
import com.dlc.shop.bean.enums.FlowVisitEnum;
import com.dlc.shop.bean.enums.SystemEnum;
import com.dlc.shop.bean.model.*;
import com.dlc.shop.bean.param.CustomerRetainRespParam;
import com.dlc.shop.bean.param.CustomerRetainedReqParam;
import com.dlc.shop.bean.param.FlowAnalysisParam;
import com.dlc.shop.bean.param.FlowUserAnalysisParam;
import com.dlc.shop.common.bean.PaySettlementConfig;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.util.Arith;
import com.dlc.shop.common.util.Json;
import com.dlc.shop.dao.FlowUserAnalysisMapper;
import com.dlc.shop.service.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.lionsoul.ip2region.xdb.Searcher;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;


/**
 *
 *
 * @author YXF
 * @date 2020-07-17 09:50:13
 */
@Slf4j
@Service
@AllArgsConstructor
public class FlowUserAnalysisServiceImpl extends ServiceImpl<FlowUserAnalysisMapper, FlowUserAnalysis> implements FlowUserAnalysisService {

    private final FlowUserAnalysisMapper flowUserAnalysisMapper;
    private final AreaService areaService;
    private final UserService userService;
    private final OrderDataAnalysisService orderDataAnalysisService;
    private final FlowLogService flowLogService;
    private final SysConfigService sysConfigService;
    private final Searcher searcher;

    /**
     * 访问页面编号数量 8
     */
    private static final int VISIT_NUM = 8;


    /**
     * (编号:访问页面数量)1:1 2:2 3:3 4:4 5:5 6:6-10 7:11-20 8:20+
     */
    private static final int[] PAGE_NUM = {0, 1, 2, 3, 4, 5, 6, 11, 20};

    @Override
    public void statisticalUserData(Map<String, List<FlowLog>> flowAnalysisLogMap, Date startTime, Map<String, FlowUserAnalysis> flowUserAnalysisMap) {
        //访客数据 -》 会话
        //初步处理数据，以及统计（加购，停留时长，用户id，访问过的商品信息）
        for (String uuidSession:flowAnalysisLogMap.keySet()) {
            List<FlowLog> flowLogList = flowAnalysisLogMap.get(uuidSession);
            flowLogList.sort(Comparator.comparingInt(FlowLog::getStep).reversed());
            Map<String, String> userIdMap = new HashMap<>(flowAnalysisLogMap.keySet().size());
            // 预防uuidSession重复导致统计出现异常
            for (FlowLog flowLog : flowLogList) {
                if (Objects.nonNull(flowLog.getUserId()) && !userIdMap.containsKey(flowLog.getUuid())) {
                    userIdMap.put(flowLog.getUuid(), flowLog.getUserId());
                }
            }

            Map<String, List<FlowLog>> uuidMap = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getUuid));

            for (Map.Entry<String, List<FlowLog>> entry : uuidMap.entrySet()) {
                String uuid = entry.getKey();
                List<FlowLog> flowLogs = entry.getValue();
                FlowLog flowLog = flowLogs.get(0);
                if (!flowUserAnalysisMap.containsKey(uuid)) {
                    flowUserAnalysisMap.put(uuid,getNewFlowUserAnalysis(flowLog));
                }
                FlowUserAnalysis flowUserAnalysis = flowUserAnalysisMap.get(uuid);
                flowUserAnalysis.getSessionSet().add(uuidSession);
                //存在用户id则使用用户id，没有则使用UUID（用户没有登陆时，没有userId）
                if (StrUtil.isBlank(flowUserAnalysis.getUserId())) {
                    flowUserAnalysis.setUserId(userIdMap.getOrDefault(flowLog.getUuid(), flowLog.getUuid()));
                }

                for (FlowLog log:flowLogs){
                    //用户的加购数量
                    if (FlowVisitEnum.SHOP_CAT.value().equals(log.getVisitType())) {
                        flowUserAnalysis.setPlusShopCart(flowUserAnalysis.getPlusShopCart() + 1);
                    }
                    //用户的停留时间
                    if (FlowVisitEnum.isVisitOrShare(log.getVisitType())) {
                        // 数据类型是页面，访问量+1
                        flowUserAnalysis.setVisitNums(flowUserAnalysis.getVisitNums() +  1);
                        flowUserAnalysis.setStopTime(flowUserAnalysis.getStopTime() + log.getStopTime());
                    }
                    //访问商品id的json
                    if (FlowLogPageEnum.isProdInfo(log.getPageId()) && StrUtil.isNotBlank(log.getBizData())) {
                        flowUserAnalysis.getVisitProdSet().add(log.getBizData());
                    }
                }
            }
        }
        //储存用户数据的集合
        List<FlowUserAnalysis> flowUserAnalysisList = Lists.newArrayList();
        //Map<用户id, List<FlowUserAnalysis>>
        Map<String, List<FlowUserAnalysis>> userAnalysisMap = flowUserAnalysisMap.values().stream().collect(Collectors.groupingBy(FlowUserAnalysis::getUserId));
        //根据用户id查询用户数据，判断新旧用户
        Map<String, User> userMap = userService.getUserListByUserNumbers(userAnalysisMap.keySet());
        // 统计每个用户的操作数据
        for (String userId:userAnalysisMap.keySet()){
            //Map<系统类型, List<FlowUserAnalysis>>
            Map<Integer, List<FlowUserAnalysis>> userAnalysisBySystemType = userAnalysisMap.get(userId).stream().collect(Collectors.groupingBy(FlowUserAnalysis::getSystemType));
            // 统计用户在不同端中的操作数据
            for (Integer systemType:userAnalysisBySystemType.keySet()) {
                //同一个用户，在不同端的操作记录，分别统计
                List<FlowUserAnalysis> userAnalysisList = userAnalysisBySystemType.get(systemType);
                statisticsUserAnalysisDate(flowUserAnalysisList,userAnalysisList,startTime,userMap,userId);
            }
        }
        if (CollUtil.isNotEmpty(flowUserAnalysisList)) {
            // 先删除统计时间内的数据，以当前统计为准
            remove(new LambdaQueryWrapper<FlowUserAnalysis>().eq(FlowUserAnalysis::getCreateDate, flowUserAnalysisList.get(0).getCreateDate()));

            //批量保存数据
            saveBatch(flowUserAnalysisList);
        }
    }

    /**
     * 统计用户数据
     * @param flowUserAnalysisList 储存用户数据的集合
     * @param userAnalysisList 用户操作记录的集合
     * @param startTime 开始时间
     * @param userMap 结束时间
     * @param userId 用户id
     */
    private void statisticsUserAnalysisDate(List<FlowUserAnalysis> flowUserAnalysisList, List<FlowUserAnalysis> userAnalysisList,Date startTime,
                                            Map<String, User> userMap,String userId) {
        //初始化-》先创建一个统计数据的对象
        FlowUserAnalysis flowUserAnalysis = userAnalysisList.get(0);
        flowUserAnalysis.setCreateDate(startTime);
        flowUserAnalysis.setSystemType(flowUserAnalysis.getSystemType());
        flowUserAnalysis.setSessionNums(flowUserAnalysis.getSessionSet().size());
        boolean isOldUser = true;
        // 从第二条数据开始累加各个字段的和
        for (int i= 1;i<userAnalysisList.size();i++){
            //在已有数据对象的基础上再进行数据统计
            FlowUserAnalysis userAnalysis = userAnalysisList.get(i);
            flowUserAnalysis.setVisitNums(flowUserAnalysis.getVisitNums() + userAnalysis.getVisitNums());
            flowUserAnalysis.setStopTime(flowUserAnalysis.getStopTime() + userAnalysis.getStopTime());
            flowUserAnalysis.setPlusShopCart(flowUserAnalysis.getPlusShopCart() + userAnalysis.getPlusShopCart());
            flowUserAnalysis.setPayAmount(Arith.add(flowUserAnalysis.getPayAmount(), userAnalysis.getPayAmount()));
            flowUserAnalysis.setPlaceOrderAmount(Arith.add(flowUserAnalysis.getPlaceOrderAmount(),userAnalysis.getPlaceOrderAmount()));
            flowUserAnalysis.getVisitProdSet().addAll(userAnalysis.getVisitProdSet());
        }
        //判断新旧用户-》根据用户注册的时间判断
        if (userMap.containsKey(userId)){
            User userDb = userMap.get(userId);
            long regtime = userDb.getUserRegtime().getTime();
            long endTime = DateUtil.offsetDay(startTime, 1).getTime();
            if (startTime.getTime() <= regtime && regtime < endTime){
                isOldUser = false;
            }
        }
        if (isOldUser){
            flowUserAnalysis.setNewUser(0);
        }else {
            flowUserAnalysis.setNewUser(1);
        }
        //用户访问商品集合的json
        String prodIdJson = Json.toJsonString(flowUserAnalysis.getVisitProdSet());
        flowUserAnalysis.setVisitProdIds(prodIdJson);
        flowUserAnalysisList.add(flowUserAnalysis);
    }

    @Override
    public void statisticalPlaceOrderOrPayData(FlowLog flowLog, Order order, Map<String, FlowUserAnalysis> flowUserAnalysisMap) {
        if (!flowUserAnalysisMap.containsKey(flowLog.getUuid())){
            flowUserAnalysisMap.put(flowLog.getUuid(),getNewFlowUserAnalysis(flowLog));
        }
        FlowUserAnalysis flowUserAnalysis = flowUserAnalysisMap.get(flowLog.getUuid());
        // 下单金额
        if (Objects.equals(flowLog.getPageId(), FlowLogPageEnum.PAY.value())) {
            flowUserAnalysis.setPlaceOrderAmount(Arith.add(flowUserAnalysis.getPlaceOrderAmount(),order.getActualTotal()));
        }
        // 支付金额
        if (Objects.equals(flowLog.getPageId(), FlowLogPageEnum.PAY_SUCCESS.value())) {
            flowUserAnalysis.setPayAmount(Arith.add(flowUserAnalysis.getPayAmount(),order.getActualTotal()));
        }
//        FlowUserAnalysis flowUserAnalysis = flowUserAnalysisMap.get(flowLog.getUuid());
//        if (!order.getStatus().equals(OrderStatus.UNPAY.value())){
//            flowUserAnalysis.setPayAmount(Arith.add(flowUserAnalysis.getPayAmount(),order.getActualTotal()));
//        }
//        flowUserAnalysis.setPlaceOrderAmount(Arith.add(flowUserAnalysis.getPlaceOrderAmount(),order.getActualTotal()));
    }

    @Override
    public void getUserAnalysisData(FlowUserAnalysisParam flowUserAnalysisParam) {
        // 访问深度Map
        Map<Integer,FlowUserAnalysisParam.VisitNum> visitPageNumMap = new HashMap<>(16);
        //访客地域分布Map
        LinkedHashMap<Long,FlowUserAnalysisDto> flowUserAnalysisDtoMap = new LinkedHashMap<>(16);
        Map<Long,FlowUserAnalysisDto> nullAreaUserAnalysisMap = new HashMap<>(16);
        FlowUserAnalysisParam.System system = new FlowUserAnalysisParam.System();
        Map<Long, Area> areaMap = areaService.getProvinceInfoList().stream().collect(Collectors.toMap(Area::getAreaId, area -> area));
        List<FlowUserAnalysis> userAnalysisDataList = flowUserAnalysisMapper.getUserAnalysisData(flowUserAnalysisParam.getStartTime(), flowUserAnalysisParam.getEndTime());
        for (FlowUserAnalysis flowUserAnalysis:userAnalysisDataList){
            if (StrUtil.isBlank(flowUserAnalysis.getUserId())) {
                continue;
            }
            //访问深度
            FlowUserAnalysisParam.VisitNum visitNum;
            Integer visitId = getVisitIdByVisitNums(flowUserAnalysis.getVisitNums());
            if (!visitPageNumMap.containsKey(visitId)){
                visitNum = new FlowUserAnalysisParam.VisitNum(visitId,0);
                visitPageNumMap.put(visitId,visitNum);
            }
            visitNum = visitPageNumMap.get(visitId);
            visitNum.setUserNums(visitNum.getUserNums() + 1);

            //访客地域分布
            Long provinceId = flowUserAnalysis.getProvinceId();
            if (!flowUserAnalysisDtoMap.containsKey(provinceId)){
                FlowUserAnalysisDto flowUserAnalysisDto = new FlowUserAnalysisDto();
                flowUserAnalysisDto.setVisitNums(0);
                flowUserAnalysisDto.setVisitUser(0);
                flowUserAnalysisDto.setStopTime(0L);
                flowUserAnalysisDto.setProdVisitNums(0);
                flowUserAnalysisDto.setProdVisitUser(0);
                flowUserAnalysisDto.setProvinceId(provinceId);
                Area area = areaMap.get(provinceId);
                if (Objects.nonNull(area)) {
                    flowUserAnalysisDto.setProvinceName(area.getAreaName());
                    flowUserAnalysisDtoMap.put(provinceId, flowUserAnalysisDto);
                } else if (CollUtil.isEmpty(nullAreaUserAnalysisMap)) {
                    flowUserAnalysisDto.setProvinceName("未知");
                    nullAreaUserAnalysisMap.put(provinceId, flowUserAnalysisDto);
                }
            }
            FlowUserAnalysisDto flowUserAnalysisDto = flowUserAnalysisDtoMap.get(provinceId) == null ? nullAreaUserAnalysisMap.get(provinceId) : flowUserAnalysisDtoMap.get(provinceId) ;
            //停留时长
            flowUserAnalysisDto.setStopTime(Objects.nonNull(flowUserAnalysisDto.getStopTime()) ? flowUserAnalysisDto.getStopTime() + flowUserAnalysis.getStopTime() : 0L + flowUserAnalysis.getStopTime());
            //访问量
            flowUserAnalysisDto.setVisitNums(flowUserAnalysisDto.getVisitNums() + flowUserAnalysis.getVisitNums());
            //访问商品id集合
            flowUserAnalysisDto.getVisitProdSet().addAll(Json.parseArray(flowUserAnalysis.getVisitProdIds(), String[].class));
            //访问用户id集合
            flowUserAnalysisDto.getVisitUserSet().add(flowUserAnalysis.getUserId());

            //系统设备访客大小统计
            switch (SystemEnum.instance(flowUserAnalysis.getSystemType())){
                case PC: system.getPc().add(flowUserAnalysis.getUserId()); break;
                case H5: system.getH5().add(flowUserAnalysis.getUserId()); break;
                case APPLETS: system.getApplets().add(flowUserAnalysis.getUserId()); break;
                case ANDROID: system.getAndroid().add(flowUserAnalysis.getUserId()); break;
                case IOS: system.getIos().add(flowUserAnalysis.getUserId()); break;
                default: break;
            }
        }
        flowUserAnalysisDtoMap.putAll(nullAreaUserAnalysisMap);
        //系统设备数据统计
        systemDataAnalysis(flowUserAnalysisParam, system, userAnalysisDataList);
        List<FlowUserAnalysisParam.VisitNum> visitNumList = Lists.newArrayList();
        for (int i = 1; i <= VISIT_NUM;i++){
            if(visitPageNumMap.containsKey(i)){
                visitNumList.add(visitPageNumMap.get(i));
            }else {
                FlowUserAnalysisParam.VisitNum visitNum = new FlowUserAnalysisParam.VisitNum(Integer.valueOf(i), 0);
                visitNumList.add(visitNum);
            }
        }
        //处理好的数据进行最终的统计
        combinationFinalStatistics(flowUserAnalysisParam, flowUserAnalysisDtoMap);
        flowUserAnalysisParam.setVisitPageList(visitNumList);
    }

    private void combinationFinalStatistics(FlowUserAnalysisParam flowUserAnalysisParam, Map<Long, FlowUserAnalysisDto> flowUserAnalysisDtoMap) {
        List<FlowUserAnalysisDto> flowUserAnalysisDtoList = Lists.newArrayList();
        for (FlowUserAnalysisDto flowUserAnalysisDto:flowUserAnalysisDtoMap.values()){
            flowUserAnalysisDto.setVisitUser(flowUserAnalysisDto.getVisitUserSet().size());
            flowUserAnalysisDto.setVisitUserSet(null);
            flowUserAnalysisDto.setProdVisitUser(flowUserAnalysisDto.getProdVisitSet().size());
            flowUserAnalysisDto.setProdVisitSet(null);
            flowUserAnalysisDto.setProdVisitNums(flowUserAnalysisDto.getVisitProdSet().size());
            flowUserAnalysisDto.setVisitProdSet(null);
            flowUserAnalysisDtoList.add(flowUserAnalysisDto);
        }
        flowUserAnalysisParam.setVisitUserList(flowUserAnalysisDtoList);
    }

    private void systemDataAnalysis(FlowUserAnalysisParam flowUserAnalysisParam, FlowUserAnalysisParam.System system, List<FlowUserAnalysis> userAnalysisDataList) {
        flowUserAnalysisParam.setPc(system.getPc().size());
        flowUserAnalysisParam.setH5(system.getH5().size());
        flowUserAnalysisParam.setApplets(system.getApplets().size());
        flowUserAnalysisParam.setAndroid(system.getAndroid().size());
        flowUserAnalysisParam.setIos(system.getIos().size());
        if(CollUtil.isEmpty(userAnalysisDataList)){
            flowUserAnalysisParam.setPc(0);
            flowUserAnalysisParam.setH5(0);
            flowUserAnalysisParam.setApplets(0);
            flowUserAnalysisParam.setAndroid(0);
            flowUserAnalysisParam.setIos(0);
            List<FlowUserAnalysisParam.VisitNum> visitNumList = Lists.newArrayList();
            for (int i = 1;i <= VISIT_NUM;i++){
                visitNumList.add(new FlowUserAnalysisParam.VisitNum(Integer.valueOf(i),0));
            }
            flowUserAnalysisParam.setVisitPageList(visitNumList);
        }
    }

    @Override
    public FlowAnalysisDto getFlowAnalysisData(FlowAnalysisParam flowAnalysisParam) {
        FlowAnalysisDto flowAnalysisDto = null;
        if (!flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.REAL_TIME.value())){
            //获取用于跟新数据比较的旧数据的开始时间
            Date startTime = getOldStartTime(flowAnalysisParam.getTimeType(),flowAnalysisParam.getStartTime());
            //获取数据列表
            //时间端：（startTime(旧数据开始时间)--->旧数据区间<---flowAnalysisParam.startTime(新数据开始时间)--->新数据区间<---flowAnalysisParam.endTime(新数据结束时间)）
            List<FlowAnalysisDto> flowAnalysisData = flowUserAnalysisMapper.getFlowAnalysisData(flowAnalysisParam,startTime);
            //同时有旧数据和新数据的size为2
            int size = 2;
            if(flowAnalysisData.size() != size){
                return new FlowAnalysisDto();
            }
            //索引 -》 0：新数据  1：旧数据
            flowAnalysisData.sort(Comparator.comparingInt(FlowAnalysisDto::getDataType));
            for (FlowAnalysisDto flowAnalysis:flowAnalysisData){
                //统计数据库中无法获取的数据
                flowAnalysis.setAverageVisitNums(Arith.div(flowAnalysis.getVisitNums(),flowAnalysis.getUserNums(),2));
                flowAnalysis.setAverageStopTime(Arith.div(flowAnalysis.getStopTime(),flowAnalysis.getUserNums(),2));
                flowAnalysis.setLossUserRate(Arith.mul(Arith.div(flowAnalysis.getLossUser(),flowAnalysis.getUserNums(),2),100));
                flowAnalysis.setPayRate(Arith.mul(Arith.div(flowAnalysis.getPayUser(),flowAnalysis.getUserNums(),2),100));
            }
            // 统计数据增减的比例
            flowAnalysisDto = flowAnalysisData.get(0);
            calculateProportion(flowAnalysisDto,flowAnalysisData.get(1));
        }else {
            //实时数据
            List<FlowLog> flowLogList = getFlowListForCache(flowAnalysisParam.getSystemType());
            //访问量 == 数据数量（一条数据代表访问一次）
            Integer visitNum =flowLogList.size();
            // 根据userId和uuid， 对访客去重
            Integer userNum = statisticsUserNum(flowLogList);
            Map<String, List<FlowLog>> payUserMap = flowLogList.stream().filter(f -> FlowLogPageEnum.PAY_SUCCESS.value().equals(f.getPageId())).collect(Collectors.groupingBy(FlowLog::getUserId));
            Integer payUser = payUserMap.size();

            //获取当日0点时间
            flowAnalysisDto = new FlowAnalysisDto();
            flowAnalysisDto.setVisitNums(visitNum);
            flowAnalysisDto.setUserNums(userNum);
            flowAnalysisDto.setAverageVisitNums(Arith.div(visitNum,userNum,2));
            flowAnalysisDto.setPayUser(payUser);
        }
        return flowAnalysisDto;
    }

    @Override
    public List<FlowAnalysisDto> flowTrend(FlowAnalysisParam flowAnalysisParam) {
        if (flowAnalysisParam.getTimeType().equals(1)){
            //自然日的趋势数据，需要所选日期近30日的数据，所以开始时间设置为30天前
            flowAnalysisParam.setStartTime(DateUtil.offsetDay(flowAnalysisParam.getStartTime(), -30));
        }
        List<FlowAnalysisDto> flowAnalysisData = Lists.newArrayList();
        if (!flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.REAL_TIME.value())){
            getDataByTime(flowAnalysisParam, flowAnalysisData);
        }else {
            //实时数据
            flowAnalysisData = Lists.newArrayList();
            List<FlowLog> flowLogList = getFlowListForCache(flowAnalysisParam.getSystemType());
            if (CollUtil.isEmpty(flowLogList)){
                return flowAnalysisData;
            }
            Map<Integer, List<FlowLog>> flowLogListAsHour = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getHour));
            Set<Integer> keySet = flowLogListAsHour.keySet();
            Integer maxHour = DateUtil.hour(new Date(),true);
            Date date = DateUtil.beginOfDay(new Date());
            for (int i = 1;i<=maxHour;i++) {
                Long minDate = DateUtil.offsetHour(date, i).getTime();
                Long maxDate = DateUtil.offsetHour(date, i+1).getTime();
                Integer userNums = 0;
                Integer visitNums = 0;
                Integer payUser = 0;
                FlowAnalysisDto flowAnalysis = new FlowAnalysisDto();
                flowAnalysis.setDateTime(String.valueOf(i) + ":00");
                if (keySet.contains(i)){
                    List<FlowLog> flowLogs = flowLogListAsHour.get(i);
                    // 访问量、支付人数
                    visitNums = flowLogs.size();
                    Map<String, List<FlowLog>> payUserMap = flowLogList.stream()
                            .filter(f -> FlowLogPageEnum.PAY_SUCCESS.value().equals(f.getPageId())
                                    && f.getCreateTime().getTime() >= minDate && f.getCreateTime().getTime() < maxDate)
                            .collect(Collectors.groupingBy(FlowLog::getUserId));
                    payUser = payUserMap.size();
                    userNums = flowLogs.stream().collect(Collectors.groupingBy(FlowLog::getUuid)).size();
                }
                flowAnalysis.setUserNums(userNums);
                flowAnalysis.setVisitNums(visitNums);
                flowAnalysis.setPayUser(payUser);
                flowAnalysis.setAverageVisitNums(Arith.div(visitNums,userNums,2));
                flowAnalysis.setPayRate(Arith.div(payUser, userNums, 4));
                flowAnalysisData.add(flowAnalysis);
            }
        }
        return flowAnalysisData;
    }

    private void getDataByTime(FlowAnalysisParam flowAnalysisParam, List<FlowAnalysisDto> flowAnalysisData) {
        //获取数据
        List<FlowAnalysisDto> flowAnalysisList = flowUserAnalysisMapper.flowTrend(flowAnalysisParam);
        int size = flowAnalysisList.size();
        //计算，通过已有数据获取完整数据
        for (int i = 0;i<size;i++){
            //统计数据库中无法获取的数据
            FlowAnalysisDto flowAnalysis = flowAnalysisList.get(i);
            flowAnalysis.setLossUserRate(Arith.div(flowAnalysis.getLossUser(),flowAnalysis.getUserNums(),2));
            flowAnalysis.setPayRate(Arith.div(flowAnalysis.getPayUser(),flowAnalysis.getUserNums(),4));
            flowAnalysis.setAverageStopTime(Arith.div(flowAnalysis.getStopTime(),flowAnalysis.getUserNums(),2));
            flowAnalysis.setAverageVisitNums(Arith.div(flowAnalysis.getVisitNums(),flowAnalysis.getUserNums(),2));
        }
        //制作表格显示的数据（补充上数据库中没有获取到的时间日期）
        Date startTime= flowAnalysisParam.getStartTime();
        Date endTime = flowAnalysisParam.getEndTime();
        //时间类型 2:自然周 3:自然月
        Map<?, FlowAnalysisDto> flowAnalysisAsDate = null;
        if(flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.WEEK.value()) || flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.MONTH.value())){
            //自然日 Map<Date, FlowAnalysisDto>  flowAnalysisAsDate
            flowAnalysisAsDate = flowAnalysisList.stream().collect(Collectors.toMap(FlowAnalysisDto::getCreateDate, f -> f));
        }else {
            //自然周、自然月 Map<String, FlowAnalysisDto>  flowAnalysisAsDate
            flowAnalysisAsDate = flowAnalysisList.stream().collect(Collectors.toMap(FlowAnalysisDto::getCreateTime, f -> f));
        }
        //自然日、自然周、自然月 可以写在一个循环里，但每次循环都需要进行一次时间类型的判断，所以分开为多个循环
        Integer timeType = flowAnalysisParam.getTimeType();
        if (timeType.equals(FlowTimeTypeEnum.WEEK.value())){
            //自然月
            while (startTime.getTime()<endTime.getTime()){
                Integer key = DateUtil.weekOfYear(startTime);
                FlowAnalysisDto flowAnalysisDto = new FlowAnalysisDto();
                if (flowAnalysisAsDate.containsKey(key)){
                    flowAnalysisDto = flowAnalysisAsDate.get(key);
                }
                startTime = getFlowAnalysisDtoInfo(flowAnalysisDto, startTime,timeType);
                flowAnalysisData.add(flowAnalysisDto);
            }
        }else if (timeType.equals(FlowTimeTypeEnum.MONTH.value())){
            //自然周
            while (startTime.getTime()<endTime.getTime()){
                Integer key = DateUtil.month(startTime) + 1;
                FlowAnalysisDto flowAnalysisDto = new FlowAnalysisDto();
                if (flowAnalysisAsDate.containsKey(key)){
                    flowAnalysisDto = flowAnalysisAsDate.get(key);
                }
                startTime = getFlowAnalysisDtoInfo(flowAnalysisDto, startTime,timeType);
                flowAnalysisData.add(flowAnalysisDto);
            }
        }else {
            //自然日
            while (startTime.getTime()<endTime.getTime()){
                FlowAnalysisDto flowAnalysisDto = new FlowAnalysisDto();
                if (flowAnalysisAsDate.containsKey(startTime)){
                    flowAnalysisDto = flowAnalysisAsDate.get(startTime);
                }
                startTime = getFlowAnalysisDtoInfo(flowAnalysisDto, startTime,timeType);
                flowAnalysisData.add(flowAnalysisDto);
            }
        }
    }

    @Override
    public List<FlowAnalysisDto> flowSour(FlowAnalysisParam flowAnalysisParam) {
        List<FlowAnalysisDto> flowAnalysisData = null;
        //总的用户数量
        Integer userAll = 0;
        if (!flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.REAL_TIME.value())){
            //获取数据
            flowAnalysisData = flowUserAnalysisMapper.flowSour(flowAnalysisParam);
            for (FlowAnalysisDto flowAnalysisDto:flowAnalysisData){
                //统计总的用户数量
                userAll = userAll + flowAnalysisDto.getUserNums();
            }
        }else {
            flowAnalysisData = Lists.newArrayList();
            //从缓存中获取数据
            List<FlowLog> flowLogList = flowLogService.getFlowLogListByCache();
            //Map<系统类型, List<FlowLog>>
            Map<Integer, List<FlowLog>> flowLogListAsSystemType = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getSystemType));
            // 存在一笔订单在h5进入支付页面，pc支付的情况，会统计多次数值，用set进行处理过滤
            Set<String> totalOrderNumbers = new HashSet<>();
            PaySettlementConfig config = sysConfigService.getSysConfigObject(Constant.PAY_SETTLEMENT_CONFIG, PaySettlementConfig.class);
            //根据系统类型分别统计数据
            for (Integer systemType:flowLogListAsSystemType.keySet()){
                //统计用户数量（用户不退出登陆，或者不清除缓存（浏览器/后台）的情况下，uuid代表一个用户）
                int userNum = flowLogListAsSystemType.get(systemType).stream().collect(Collectors.groupingBy(FlowLog::getUuid)).size();
                FlowAnalysisDto flowAnalysisDto = new FlowAnalysisDto();
                flowAnalysisDto.setUserNums(userNum);
                userAll += userNum;
                //统计下单、支付数据
                List<FlowLog> flowLogs = flowLogListAsSystemType.get(systemType).stream().filter(f -> FlowLogPageEnum.isPayOrPaySuccess(f.getPageId())).toList();
                //订单编号集合
                Set<String> orderIds = new HashSet<>();
                for (FlowLog flowLog:flowLogs){
                    if (StrUtil.isBlank(flowLog.getBizData())) {
                        continue;
                    }
                    if(FlowLogPageEnum.PAY.value().equals(flowLog.getPageId()) || FlowLogPageEnum.PAY_SUCCESS.value().equals(flowLog.getPageId())){
                        orderIds.addAll(Arrays.asList(flowLog.getBizData().split(",")));
                    }
                }
                Set<String> placeOrderUserSet = new HashSet<>();
                Set<String> payUserSet = new HashSet<>();
                double placeOrderAmount = 0.0;
                double payAmount = 0.0;
                if (CollUtil.isNotEmpty(orderIds)){
                    List<Order> orderList = orderDataAnalysisService.getOrderAndOrderItemByOrderNumberList(orderIds, config.getPaySettlementType());
                    for (Order order:orderList){
                        if (totalOrderNumbers.contains(order.getOrderNumber())) {
                            continue;
                        }
                        totalOrderNumbers.add(order.getOrderNumber());
                        //统计下单信息
                        placeOrderUserSet.add(order.getUserId());
                        placeOrderAmount = Arith.add(placeOrderAmount,order.getActualTotal());
                        //如果该订单已支付，则统计支付信息
                        if (order.getIsPayed() == 1) {
                            payUserSet.add(order.getUserId());
                            payAmount = Arith.add(payAmount,order.getActualTotal());
                        }
                    }
                }
                Map<String, List<FlowLog>> plusShopCartMap = flowLogListAsSystemType.get(systemType).stream()
                        .filter(f -> Objects.equals(f.getVisitType(), FlowVisitEnum.SHOP_CAT.value()))
                        .collect(Collectors.groupingBy(FlowLog::getUuid));
                flowAnalysisDto.setPlusShopCartUser(plusShopCartMap.size());
                flowAnalysisDto.setSystemType(systemType);
                flowAnalysisDto.setPlaceOrderUser(placeOrderUserSet.size());
                flowAnalysisDto.setPlaceOrderAmount(placeOrderAmount);
                flowAnalysisDto.setPayUser(payUserSet.size());
                flowAnalysisDto.setPayAmount(payAmount);
                flowAnalysisData.add(flowAnalysisDto);
            }
        }
        //访客数占比
        for (FlowAnalysisDto flowAnalysisDto:flowAnalysisData){
            //统计数据库中没有的数据
            flowAnalysisDto.setUvPrice(Arith.div(flowAnalysisDto.getPayAmount(),flowAnalysisDto.getUserNums(),2));
            flowAnalysisDto.setCustomerPrice(Arith.div(flowAnalysisDto.getPayAmount(),flowAnalysisDto.getPayUser(),2));
            flowAnalysisDto.setPlaceOrderRate(Arith.div(flowAnalysisDto.getPlaceOrderUser().doubleValue(),flowAnalysisDto.getUserNums().doubleValue(),4));
            flowAnalysisDto.setPayRate(Arith.div(flowAnalysisDto.getPayUser().doubleValue(),flowAnalysisDto.getUserNums().doubleValue(),4));
            flowAnalysisDto.setUserNumsRatio(Arith.div(flowAnalysisDto.getUserNums(),userAll.doubleValue(),2));
        }
        return flowAnalysisData;
    }

    @Override
    public SystemDto systemTypeNums(FlowAnalysisParam flowAnalysisParam) {
        SystemDto systemDto = new SystemDto();
        if (!flowAnalysisParam.getTimeType().equals(FlowTimeTypeEnum.REAL_TIME.value())){
            //获取数据（数据库）
            List<SystemDto> systemDtoList = flowUserAnalysisMapper.systemTypeNums(flowAnalysisParam);
            for (SystemDto system:systemDtoList){
                switch (system.getSystemType()){
                    case 0: systemDto.setAll(system.getUserNum());break;
                    case 1: systemDto.setPc(system.getUserNum());break;
                    case 2: systemDto.setH5(system.getUserNum());break;
                    case 3: systemDto.setApplets(system.getUserNum());break;
                    case 4: systemDto.setAndroid(system.getUserNum());break;
                    case 5: systemDto.setIos(system.getUserNum());break;
                    default: break;
                }
            }
        }else {
            systemDto = new SystemDto();
            List<FlowLog> flowLogList = flowLogService.getFlowLogListByCache().stream().filter(f->FlowVisitEnum.isVisitOrShare(f.getVisitType())).toList();
            //Map<系统类型, List<FlowLog>>
            Map<Integer, List<FlowLog>> flowLogListAsSystemType = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getSystemType));
            // 0:全部
            Integer userNum = statisticsUserNum(flowLogList);
            systemDto.setAll(userNum);
            // 1:pc  2:h5  3:小程序 4:安卓  5:ios
            for (Integer systemType:flowLogListAsSystemType.keySet()){
                Map<String, List<FlowLog>> flowLogListAsUuid = flowLogListAsSystemType.get(systemType).stream().collect(Collectors.groupingBy(FlowLog::getUuid));
                if (systemType.equals(1)){
                    systemDto.setPc(flowLogListAsUuid.size());
                }else if (systemType.equals(2)){
                    systemDto.setH5(flowLogListAsUuid.size());
                }else if (systemType.equals(3)){
                    systemDto.setApplets(flowLogListAsUuid.size());
                }else if (systemType.equals(4)){
                    systemDto.setAndroid(flowLogListAsUuid.size());
                }else if (systemType.equals(5)){
                    systemDto.setIos(flowLogListAsUuid.size());
                }
            }
        }
        return systemDto;
    }

    @Override
    @Cacheable(cacheNames = "visitRetained", key = "#param.retainType +':'+ #param.dateType")
    public List<CustomerRetainRespParam> getVisitRetained(CustomerRetainedReqParam param) {
        return flowUserAnalysisMapper.getVisitRetained(param);
    }

    @Override
    @CacheEvict(cacheNames = "visitRetained", key = "#param.retainType +':'+ #param.dateType")
    public void removeCacheVisitRetained(CustomerRetainedReqParam param) {
    }

    @Override
    public Integer countAddCartUserNum(Date startTime, Date endTime) {
        return flowUserAnalysisMapper.countAddCartUserNum(startTime, endTime);
    }

    /**
     * 获取用户操作数据（今日实时）
     * @param systemType
     * @return
     */
    private List<FlowLog>  getFlowListForCache(Integer systemType){
        //从缓存中获取数据
        List<FlowLog> flowLogList = flowLogService.getFlowLogListByCache();
        //保留访问类型的操作数据
        flowLogList = flowLogList.stream().filter(f->FlowVisitEnum.isVisitOrShare(f.getVisitType())).collect(Collectors.toList());
        //如果有选择指定系统类型，则只指定系统类型的数据
        if (!systemType.equals(0)){
            flowLogList = flowLogList.stream().filter(flowLog -> flowLog.getSystemType().equals(systemType)).collect(Collectors.toList());
        }
        for (FlowLog flowLog : flowLogList) {
            if (Objects.isNull(flowLog.getHour())) {
                flowLog.setHour(DateUtil.hour(flowLog.getCreateTime(), true));
            }
        }
        return flowLogList;
    }

    /**
     * 统计数据的增减比例
     * @param flowAnalysisDto 新数据
     * @param flowAnalysisDtoOld 旧数据
     */
    private void calculateProportion(FlowAnalysisDto flowAnalysisDto, FlowAnalysisDto flowAnalysisDtoOld) {
        FlowAnalysisDto.Ratio ratio = new FlowAnalysisDto.Ratio();
        flowAnalysisDto.setRatio(ratio);
        //获取比例大小
        ratio.setNewUserRatio(ratio.calculateProportion(flowAnalysisDto.getNewUser().doubleValue(),flowAnalysisDtoOld.getNewUser().doubleValue()));
        ratio.setLossUserRatio(ratio.calculateProportion(flowAnalysisDto.getLossUserRate(),flowAnalysisDtoOld.getLossUserRate()));
        ratio.setUserNumsRatio(ratio.calculateProportion(flowAnalysisDto.getUserNums().doubleValue(),flowAnalysisDtoOld.getUserNums().doubleValue()));
        ratio.setVisitNumsRatio(ratio.calculateProportion(flowAnalysisDto.getVisitNums().doubleValue(),flowAnalysisDtoOld.getVisitNums().doubleValue()));
        ratio.setAverageStopTimeRatio(ratio.calculateProportion(flowAnalysisDto.getAverageStopTime(),flowAnalysisDtoOld.getAverageStopTime()));
        ratio.setAverageVisitNumsRatio(ratio.calculateProportion(flowAnalysisDto.getAverageVisitNums(),flowAnalysisDtoOld.getAverageVisitNums()));
        ratio.setPayUserRatio(ratio.calculateProportion(flowAnalysisDto.getPayUser().doubleValue(),flowAnalysisDtoOld.getPayUser().doubleValue()));
        ratio.setPayRateRatio(ratio.calculateProportion(flowAnalysisDto.getPayRate(),flowAnalysisDtoOld.getPayRate()));
        //判断比例的增减
        if (ratio.getNewUserRatio() < 0){
            ratio.setNewUserRatioRise(false);
            ratio.setNewUserRatio(Arith.mul(ratio.getNewUserRatio(),-1));
        }
        if (ratio.getLossUserRatio() < 0){
            ratio.setLossUserRatioRise(false);
            ratio.setLossUserRatio(Arith.mul(ratio.getLossUserRatio(),-1));
        }
        if (ratio.getUserNumsRatio() < 0){
            ratio.setUserNumsRatioRise(false);
            ratio.setUserNumsRatio(Arith.mul(ratio.getUserNumsRatio(),-1));
        }
        if (ratio.getVisitNumsRatio() < 0){
            ratio.setVisitNumsRatioRise(false);
            ratio.setVisitNumsRatio(Arith.mul(ratio.getVisitNumsRatio(),-1));
        }
        if (ratio.getAverageStopTimeRatio() < 0){
            ratio.setAverageStopTimeRatioRise(false);
            ratio.setAverageStopTimeRatio(Arith.mul(ratio.getAverageStopTimeRatio(),-1));
        }
        if (ratio.getAverageVisitNumsRatio() < 0){
            ratio.setAverageVisitNumsRatioRise(false);
            ratio.setAverageVisitNumsRatio(Arith.mul(ratio.getAverageVisitNumsRatio(),-1));
        }
        if (ratio.getPayUserRatio() < 0){
            ratio.setPayUserRatioRise(false);
            ratio.setPayUserRatio(Arith.mul(ratio.getPayUserRatio(),-1));
        }
        if (ratio.getPayRateRatio() < 0){
            ratio.setPayRateRatioRise(false);
            ratio.setPayRateRatio(Arith.mul(ratio.getPayRateRatio(),-1));
        }
    }

    /**
     *  获取访问编号
     * @param visitNums  (编号:访问页面数量)1:1 2:2 3:3 4:4 5:5 6:6-10 7:11-20 8:20+
     * @return
     */
    private Integer getVisitIdByVisitNums(Integer visitNums) {
       /* if (visitNums < 6){
            return visitNums;
        } else if ((visitNums >= 6 && visitNums <= 10)){
            //6-10个页面
            return 6;
        } else if ((visitNums >= 11 && visitNums <= 20)){
            //11-20个页面
            return 7;
        }*/


        // PAGE_NUM visitNums小于等于5时返回visitNums在PAGE_NUM中的位置, 6-10返回6, 11-20返回7, 20+返回8
        for (int i = PAGE_NUM.length - 1; i > 0; i--) {
            if (visitNums >= PAGE_NUM[i]) {
                return i;
            }
        }

        return 8;
    }

    private Long getProvinceIdByUserIp(String ip) {
        List<Area> areaList = areaService.getProvinceInfoList();
        String province = "";

        // 根据IP搜索地址信息
        try {
            // B树搜索（更快）
            String region = searcher.search(ip);
            province = region.split("\\|")[2];
        } catch (Exception e) {
            return 0L;
        }
        for (Area area:areaList){
            if (area.getAreaName().indexOf(province) != -1){
                return area.getAreaId();
            }
        }
        return 0L;
    }

    /**
     * 获取新的用户数据对象
     * @param flowLog
     * @return
     */
    private FlowUserAnalysis getNewFlowUserAnalysis(FlowLog flowLog){
        FlowUserAnalysis flowUserAnalysis = new FlowUserAnalysis();
        flowUserAnalysis.setPlusShopCart(0);
        flowUserAnalysis.setPlaceOrderAmount(0.0);
        flowUserAnalysis.setPayAmount(0.0);
        flowUserAnalysis.setVisitNums(0);
        flowUserAnalysis.setStopTime(0L);
        flowUserAnalysis.setSystemType(flowLog.getSystemType());
        flowUserAnalysis.setSessionSet(new HashSet<>());
        flowUserAnalysis.setVisitProdSet(new HashSet<>());
        flowUserAnalysis.setProvinceId(getProvinceIdByUserIp(flowLog.getIp()));
        return flowUserAnalysis;
    }

    /**
     * 获取下一次循环的开始时间，以及设置后台显示的时间字符串
     * @param flowAnalysisDto
     * @param startTime 开始时间
     * @param type 时间类型
     * @return
     */
    private Date getFlowAnalysisDtoInfo(FlowAnalysisDto flowAnalysisDto,Date startTime,Integer type){
        if (type.equals(FlowTimeTypeEnum.WEEK.value())) {
            //时间类型 2:自然周
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(startTime);
            //设置周一为每周的开始日期
            calendar.setFirstDayOfWeek(Calendar.MONDAY);
            flowAnalysisDto.setDateTime(DateUtil.format(startTime,"yyyy第ww周"));
            startTime = DateUtil.offsetWeek(startTime,1);
        }else if (type.equals(FlowTimeTypeEnum.MONTH.value())){
            //时间类型 3:自然月
            flowAnalysisDto.setDateTime(DateUtil.format(startTime,"yyyy-MM"));
            startTime = DateUtil.offsetMonth(startTime,1);
        }else {
            flowAnalysisDto.setDateTime(DateUtil.format(startTime,"yyyy-MM-dd"));
            startTime = DateUtil.offsetDay(startTime, 1);
        }
        return startTime;
    }
    public Date getOldStartTime(Integer timeType, Date startTime){
        Date time = new Date();
        if (timeType.equals(FlowTimeTypeEnum.DAY.value()) || timeType.equals(FlowTimeTypeEnum.REAL_TIME.value())){
            time = DateUtil.offsetDay(startTime,-1);
        }else if (timeType.equals(FlowTimeTypeEnum.WEEK.value())){
            time = DateUtil.offsetWeek(startTime,-1);
        }else if (timeType.equals(FlowTimeTypeEnum.MONTH.value())){
            time = DateUtil.offsetMonth(startTime,-1);
        }else if (timeType.equals(FlowTimeTypeEnum.SEVEN_DAYS.value())){
            time = DateUtil.offsetDay(startTime,-7);
        }else if (timeType.equals(FlowTimeTypeEnum.ONE_MONTH.value())){
            time = DateUtil.offsetDay(startTime,-30);
        }
        return time;
    }

    private static Integer statisticsUserNum(List<FlowLog> flowLogList) {
        Set<String> visitorSet = new HashSet<>();
        Map<String, List<FlowLog>> flowLogMap = flowLogList.stream().collect(Collectors.groupingBy(FlowLog::getUuid));
        for (Map.Entry<String, List<FlowLog>> stringListEntry : flowLogMap.entrySet()) {
            List<FlowLog> flowLogs = stringListEntry.getValue();
            List<String> userList = flowLogs.stream().map(FlowLog::getUserId).filter(Objects::nonNull).toList();
            if (CollUtil.isNotEmpty(userList)) {
                visitorSet.add(userList.get(0));
            } else {
                visitorSet.add(stringListEntry.getKey());
            }
        }
        return visitorSet.size();
    }
}
